// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Antlr4.Runtime.Misc;
using Templating.Grammar.Antlr;
using static Templating.Grammar.Antlr.TemplatingParser;

namespace SnapObjects.Data
{
    internal partial class DataTemplateParser
        : TemplatingParserBaseVisitor<StringBuilder>
        , ITemplatingParserVisitor<StringBuilder>
    {
        public override StringBuilder VisitForeach([NotNull] ForeachContext context)
        {
            var foreachExpr = context.foreachExpr();

            var query = foreachExpr.foreachQueryExpr();

            var dataSource = this.GetEnumDataSource(query);

            dataSource = this.OrderBy(dataSource, query);

            //var expressions = foreachExpr.bodyExpr().children.ToArray();

            if (this.TryGroupBy(dataSource, query, out var groups))
            {
                var name = query.foreachIntoExpr()?.TOKEN_IDENTIFIER()?.GetText();

                foreach (var group in groups)
                {
                    _context.Model = group.First();

                    if (!String.IsNullOrWhiteSpace(name))
                    {
                        _context.Variables[name] = group;
                    }

                    //Array.ForEach(expressions, x => this.Visit(x));
                    this.Visit(foreachExpr.bodyExpr());
                }
            }
            else
            {
                foreach (var item in dataSource)
                {
                    _context.Model = item;

                    //Array.ForEach(expressions, x => this.Visit(x));
                    this.Visit(foreachExpr.bodyExpr());
                }
            }

            return _builder;
        }

        private IEnumerable<object> GetEnumDataSource(ForeachQueryExprContext query)
        {
            var source = query?.TOKEN_IDENTIFIER()?.GetText();

            if (String.IsNullOrWhiteSpace(source))
            {
                return _dataProvider.GetDataSource();
            }

            if (_context.Variables.TryGetValue(source, out var value))
            {
                if (value is IEnumerable list)
                {
                    return list.OfType<object>();
                }
                else
                {
                    throw new InvalidOperationException($"{source} cannot be iterated");
                }
            }
            else
            {
                throw new ArgumentException($"Data source not found: {source}");
            }
        }

        private IEnumerable<object> OrderBy(
            IEnumerable<object> source, ForeachQueryExprContext query)
        {
            var order = query?.orderByExpr()?.TOKEN_CONST_STRING()?.GetText();

            return _dataProvider.OrderBy(source, this.GetStringValue(order));
        }

        private bool TryGroupBy(
            IEnumerable<object> source,
            ForeachQueryExprContext query,
            out IEnumerable<IGrouping<object, object>> groups)
        {
            var group = query?.groupByExpr();

            if (group != null)
            {
                var selector = group.TOKEN_CONST_STRING().GetText();
                var order = group.orderByExpr()?.TOKEN_CONST_STRING().GetText();

                selector = this.GetStringValue(selector);
                order = this.GetStringValue(order);

                groups = _dataProvider.GroupBy(source, selector, order);

                return true;
            }

            groups = null;

            return false;
        }

        private string GetStringValue(string text)
        {
            return text?.Substring(1, text.Length - 2);
        }

        public override StringBuilder VisitBodyExpr([NotNull] BodyExprContext context)
        {
            var expressions = context.children.ToArray();

            Array.ForEach(expressions, x => this.Visit(x));

            return _builder;
        }

        public override StringBuilder VisitObjExpr([NotNull] ObjExprContext context)
        {
            _builder.Append(context.TOKEN_OP_LBRACKET().GetText());

            this.Visit(context.bodyExpr());

            return _builder.Append(context.TOKEN_OP_RBRACE().GetText());
        }
    }
}
